Setup

Libraries

# Check and install required libraries.
list.of.packages <- c(
    "dplyr", "xlsx", "readxl","lubridate", "ggplot2", 
    "kableExtra", "tidyverse", "stargazer", "gridExtra", "ggpubr",
    "formatR", "sjPlot", "reshape2", "devtools", "ggforce", "plotly")
new.packages <- list.of.packages[!(list.of.packages %in%
    installed.packages()[,"Package"])]

# Remove checked vectors from memory.
if(length(new.packages)) install.packages(new.packages)
rm(list.of.packages, new.packages)

# Note: need to remember how to set up 'patchwork', which was
# somehow done manually using devtools.

# Global chunk defaults
knitr::opts_chunk$set(echo=TRUE, message=FALSE, warning=FALSE)

Helpful Functions

# Function to paste '%' to first observation

label_percent <- function(x){
    for(i in 1:length(x)){
        if(i==1){
            x[i] <- paste(x[i], "%", sep="")
        } else{
            x[i] <- paste(x[i])
        }
    }
    return(x)
}

# Function to make bold text by pasting <b> and </b> to strings

label_bold <- function(x){
    x <- as.character(x)
    for(i in 1:length(x)){
        x[i] <- paste("<b>", as.character(x[i]), "</b>", sep="")
    }
    return(x)
}

Constants

# Constant variables

    # Student count (district)
    n_total <- as.numeric(514)
    
    # Bar plot figure dimensions
    width_bar <- as.numeric(700)
    height_bar <- as.numeric(500)

Demographics

Data

Render Visual

# Function to create figure

render_demographics <- function(df){
  
    # Load required libraries 

    # import_library('dplyr')
    # import_library('ggplot2')
    # import_library('plotly')
    require(dplyr)
    require(ggplot2)
    require(plotly)
  
    # Keep observations with valid data 
    # Note: NA values may cause issues, confirm why values are 0 instead of NA
  
    df <- filter(df, n>0)
    
    # Make group types into bold text
    
    df$type <- label_bold(df$type)
    
    # Create label format for tooltip
    
    tooltip_text <- paste0(
        "<b>Group Size:</b></br></br>", df$group_pct, 
        "%</br><b>Total Scholars</b></br>", df$n)
  
    # Define ggplot object
    
    visual <- df %>% 

        # Redefine group column as factors and set order
        # Note: includes HTML-edited type column text
      
        dplyr::mutate(group = factor(group, 
            levels = c(
                "Male",
                "Female",
                "ELL",
                "Dual-Identified",
                "Special Ed.",
                "504",
                "Afr. American",
                "Latino",
                "White",
                "Asian",
                "Am. Ind/Alask. Native",
                "Nat. Haw/Pac. Island.")))%>%
      
        dplyr::mutate(type=factor(type, 
            levels = c(
                "<b>Gender</b>",
                "<b>Special Groups</b>",
                "<b>Race/Ethnicity</b>")))%>%
  
            # Draw figure using ggplot
            # Note: geom_col == geom_bar(stat="identity", ...)
      
            ggplot(aes(x=factor(group, levels=rev(levels(group))), y=group_pct, 
                label=label_percent(group_pct), text=tooltip_text))+
                geom_col(position="dodge", color="black", aes(fill=as.factor(type)))+
                coord_flip()+
      
                # Titles and labels (title deprecated by plotly)
                # Conflict: geom_text layer with geom_bar, replaced with tooltip only
      
                labs(title="Scholar Demographics 2019-20", 
                    subtitle = "Summary Statistics of Gender, Special Groups, and Race in the District", 
                    x="", y="",
                    caption = paste(toString(demographics.df$group[demographics.df$n==0]), 
                    "sizes not reported."))+
      
                # Axis scale and labels
                # Note: controls margin of graph for axis cut-offs with constant +5
      
                scale_y_continuous(expand=c(0,0), 
                    limits = c(0, (df$group_pct%>%
                        sort(., decreasing = TRUE)%>%
                            .[1])+5), 
                    labels = function(x) paste0(x, "%")) + # []
      
                # Themes and colors (legend location deprecated by plotly unless 'none')
      
                scale_fill_brewer()+
                theme_bw()+
                theme(legend.position = "top",
                    legend.box="horizontal",
                    axis.text.x = element_text(color="black", size = 11),
                    axis.text.y = element_text(color="black", size = 11))
    
                # Apply ggplotly (plotly) functionality to ggplot object
                    # Note: use of HTML text below required to create a subtitle deprecated by conversion
                    # Note: user access to 'mode bar' completely restricted
    
                visual <- ggplotly(visual, tooltip="text", width=width_bar, height=height_bar)%>% 
                    config(displayModeBar = FALSE)%>% 
                    layout(xaxis=list(fixedrange=TRUE))%>%
                    layout(yaxis=list(fixedrange=TRUE))%>%
                    layout(title=list(text=paste0(
                        '<b>Scholar Demographics</b>',
                        '<br>',
                        '<sup>',
                        '<em>Summary of ',n_total,' Scholars in the District (2019-20)</em>',
                        '</sup>')))%>%
                    layout(annotations=list(x = 1, y = -0.1, text = 
                        "High Needs and Econ. Disadvantaged groups not reported.", 
                        showarrow = FALSE, xref='paper', yref='paper', 
                        xanchor='right', yanchor='auto', xshift=0, yshift=-10,
                        font=list(size=12, color="black")))%>%
                    layout(legend=list(orientation="h", x=0.25, y=-0.2))%>%
                    style(textposition="right")
                
    return(visual)
}

render_demographics(demographics.df)
LS0tCnRpdGxlOiAiPHN0cm9uZz5TY2hvb2wgRGF0YSBBbmFseXNpczwvc3Ryb25nPjxpbWcgc3JjPVwiYXJnb3N5X2xvZ28ucG5nXCIgc3R5bGU9XCJmbG9hdDogcmlnaHQ7XCIgd2lkdGg9XCIyMDA7XCIvPiIKYXV0aG9yOiAiPGVtPkdlcmFyZG8gUGVsYXlvIEdhcmPDrWE8L2VtPjxicj48YnI+YHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCiAgICAKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogc3BhY2VsYWIKICAgIGRmX3ByaW50OiBwYWdlZAogICAgbnVtYmVyX3NlY3Rpb25zOiBubwogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDoKICAgICAgc21vb3RoX3Njcm9sbDogeWVzCiAgaHRtbF9ub3RlYm9vazoKICAgIG51bWJlcl9zZWN0aW9uczogbm8KICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6CiAgICAgIHNtb290aF9zY3JvbGw6IHllcwogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogeWVzCi0tLQoKIyBTZXR1cCB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30KCiMjIExpYnJhcmllcwoKYGBge3Igc2V0dXB9CiMgQ2hlY2sgYW5kIGluc3RhbGwgcmVxdWlyZWQgbGlicmFyaWVzLgpsaXN0Lm9mLnBhY2thZ2VzIDwtIGMoCiAgICAiZHBseXIiLCAieGxzeCIsICJyZWFkeGwiLCJsdWJyaWRhdGUiLCAiZ2dwbG90MiIsIAogICAgImthYmxlRXh0cmEiLCAidGlkeXZlcnNlIiwgInN0YXJnYXplciIsICJncmlkRXh0cmEiLCAiZ2dwdWJyIiwKICAgICJmb3JtYXRSIiwgInNqUGxvdCIsICJyZXNoYXBlMiIsICJkZXZ0b29scyIsICJnZ2ZvcmNlIiwgInBsb3RseSIpCm5ldy5wYWNrYWdlcyA8LSBsaXN0Lm9mLnBhY2thZ2VzWyEobGlzdC5vZi5wYWNrYWdlcyAlaW4lCiAgICBpbnN0YWxsZWQucGFja2FnZXMoKVssIlBhY2thZ2UiXSldCgojIFJlbW92ZSBjaGVja2VkIHZlY3RvcnMgZnJvbSBtZW1vcnkuCmlmKGxlbmd0aChuZXcucGFja2FnZXMpKSBpbnN0YWxsLnBhY2thZ2VzKG5ldy5wYWNrYWdlcykKcm0obGlzdC5vZi5wYWNrYWdlcywgbmV3LnBhY2thZ2VzKQoKIyBOb3RlOiBuZWVkIHRvIHJlbWVtYmVyIGhvdyB0byBzZXQgdXAgJ3BhdGNod29yaycsIHdoaWNoIHdhcwojIHNvbWVob3cgZG9uZSBtYW51YWxseSB1c2luZyBkZXZ0b29scy4KCiMgR2xvYmFsIGNodW5rIGRlZmF1bHRzCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpCmBgYAoKIyMgSGVscGZ1bCBGdW5jdGlvbnMKCmBgYHtyIGhlbHBmdWwuZnVuY3Rpb25zfQojIEZ1bmN0aW9uIHRvIHBhc3RlICclJyB0byBmaXJzdCBvYnNlcnZhdGlvbgoKbGFiZWxfcGVyY2VudCA8LSBmdW5jdGlvbih4KXsKICAgIGZvcihpIGluIDE6bGVuZ3RoKHgpKXsKICAgICAgICBpZihpPT0xKXsKICAgICAgICAgICAgeFtpXSA8LSBwYXN0ZSh4W2ldLCAiJSIsIHNlcD0iIikKICAgICAgICB9IGVsc2V7CiAgICAgICAgICAgIHhbaV0gPC0gcGFzdGUoeFtpXSkKICAgICAgICB9CiAgICB9CiAgICByZXR1cm4oeCkKfQoKIyBGdW5jdGlvbiB0byBtYWtlIGJvbGQgdGV4dCBieSBwYXN0aW5nIDxiPiBhbmQgPC9iPiB0byBzdHJpbmdzCgpsYWJlbF9ib2xkIDwtIGZ1bmN0aW9uKHgpewogICAgeCA8LSBhcy5jaGFyYWN0ZXIoeCkKICAgIGZvcihpIGluIDE6bGVuZ3RoKHgpKXsKICAgICAgICB4W2ldIDwtIHBhc3RlKCI8Yj4iLCBhcy5jaGFyYWN0ZXIoeFtpXSksICI8L2I+Iiwgc2VwPSIiKQogICAgfQogICAgcmV0dXJuKHgpCn0KYGBgCgojIyBDb25zdGFudHMKCmBgYHtyIHN0YXRpYy52YXJpYWJsZXN9CiMgQ29uc3RhbnQgdmFyaWFibGVzCgogICAgIyBTdHVkZW50IGNvdW50IChkaXN0cmljdCkKICAgIG5fdG90YWwgPC0gYXMubnVtZXJpYyg1MTQpCiAgICAKICAgICMgQmFyIHBsb3QgZmlndXJlIGRpbWVuc2lvbnMKICAgIHdpZHRoX2JhciA8LSBhcy5udW1lcmljKDcwMCkKICAgIGhlaWdodF9iYXIgPC0gYXMubnVtZXJpYyg1MDApCmBgYAoKIyBEZW1vZ3JhcGhpY3Mgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9CgojIyBEYXRhCgpgYGB7ciBkZW1vZ3JhcGhpY3MuZGF0YX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGliYmxlKQoKIyBTZXQgdXAgZm9yIGhhbmQtY29waWVkIGRhdGEgZnJvbSBQREYKCmRlbW9ncmFwaGljcy5kZiA8LSBkYXRhLmZyYW1lKAogIAogICAgIyBOYW1lIG9mIGdyb3VwCiAgCiAgICAiZ3JvdXAiPWMoCiAgICAgICAgIk1hbGUiLAogICAgICAgICJGZW1hbGUiLAogICAgICAgICJFTEwiLAogICAgICAgICJTcGVjaWFsIEVkLiIsCiAgICAgICAgIkR1YWwtSWRlbnRpZmllZCIsCiAgICAgICAgIjUwNCIsCiAgICAgICAgIkFmci4gQW1lcmljYW4iLAogICAgICAgICJMYXRpbm8iLAogICAgICAgICJXaGl0ZSIsCiAgICAgICAgIkFzaWFuIiwKICAgICAgICAiQW0uIEluZC9BbGFzay4gTmF0aXZlIiwKICAgICAgICAiTmF0LiBIYXcvUGFjLiBJc2xhbmQuIiksCiAgICAKICAgICMgU3R1ZGVudCBjb3VudAogICAgCiAgICAibiI9YygKICAgICAgICAyNzksCiAgICAgICAgMjM1LAogICAgICAgIDEyOCwKICAgICAgICA5NCwKICAgICAgICA0MCwKICAgICAgICA1MSwKICAgICAgICA4OCwKICAgICAgICAxMTUsCiAgICAgICAgMzE2LAogICAgICAgIDEzLAogICAgICAgIDUsCiAgICAgICAgMiksCiAgICAKICAgICMgVHlwZSBvZiBkYXRhIHBvaW50IChjYXRlZ29yaWNhbCBncm91cGluZykKICAgIAogICAgInR5cGUiPWMoCiAgICAgICAgCiAgICAgICAgIyBGaXJzdCB0d28gdmFyaWFibGVzCiAgICAgICAgIkdlbmRlciIsCiAgICAgICAgIkdlbmRlciIsCiAgICAgICAgCiAgICAgICAgIyBOZXh0IDQKICAgICAgICAiU3BlY2lhbCBHcm91cHMiLAogICAgICAgICJTcGVjaWFsIEdyb3VwcyIsCiAgICAgICAgIlNwZWNpYWwgR3JvdXBzIiwKICAgICAgICAiU3BlY2lhbCBHcm91cHMiLAogICAgICAgIAogICAgICAgICMgTGFzdCA2CiAgICAgICAgIlJhY2UvRXRobmljaXR5IiwKICAgICAgICAiUmFjZS9FdGhuaWNpdHkiLAogICAgICAgICJSYWNlL0V0aG5pY2l0eSIsCiAgICAgICAgIlJhY2UvRXRobmljaXR5IiwKICAgICAgICAiUmFjZS9FdGhuaWNpdHkiLAogICAgICAgICJSYWNlL0V0aG5pY2l0eSIpCiAgICApCgojIFBlcmNlbnRhZ2VzIGZvciBlYWNoIGdyb3VwIHVzaW5nIG4gYW5kIHN0dWRlbnQgY291bnQuCgpkZW1vZ3JhcGhpY3MuZGYkZ3JvdXBfcGN0IDwtIHJvdW5kKGRlbW9ncmFwaGljcy5kZiRuL25fdG90YWwsIGRpZ2l0cz0yKQpkZW1vZ3JhcGhpY3MuZGYkZ3JvdXBfcGN0IDwtIGRlbW9ncmFwaGljcy5kZiRncm91cF9wY3QqMTAwCgojIFByaW50IGRhdGEgZnJhbWUgYXMgdGliYmxlCgphc190aWJibGUoZGVtb2dyYXBoaWNzLmRmKQpgYGAKCiMjIFJlbmRlciBWaXN1YWwKCmBgYHtyIGRlbW9ncmFwaGljcy5yZW5kZXJ9CiMgRnVuY3Rpb24gdG8gY3JlYXRlIGZpZ3VyZQoKcmVuZGVyX2RlbW9ncmFwaGljcyA8LSBmdW5jdGlvbihkZil7CiAgCiAgICAjIExvYWQgcmVxdWlyZWQgbGlicmFyaWVzIAoKICAgICMgaW1wb3J0X2xpYnJhcnkoJ2RwbHlyJykKICAgICMgaW1wb3J0X2xpYnJhcnkoJ2dncGxvdDInKQogICAgIyBpbXBvcnRfbGlicmFyeSgncGxvdGx5JykKICAgIHJlcXVpcmUoZHBseXIpCiAgICByZXF1aXJlKGdncGxvdDIpCiAgICByZXF1aXJlKHBsb3RseSkKICAKICAgICMgS2VlcCBvYnNlcnZhdGlvbnMgd2l0aCB2YWxpZCBkYXRhIAogICAgIyBOb3RlOiBOQSB2YWx1ZXMgbWF5IGNhdXNlIGlzc3VlcywgY29uZmlybSB3aHkgdmFsdWVzIGFyZSAwIGluc3RlYWQgb2YgTkEKICAKICAgIGRmIDwtIGZpbHRlcihkZiwgbj4wKQogICAgCiAgICAjIE1ha2UgZ3JvdXAgdHlwZXMgaW50byBib2xkIHRleHQKICAgIAogICAgZGYkdHlwZSA8LSBsYWJlbF9ib2xkKGRmJHR5cGUpCiAgICAKICAgICMgQ3JlYXRlIGxhYmVsIGZvcm1hdCBmb3IgdG9vbHRpcAogICAgCiAgICB0b29sdGlwX3RleHQgPC0gcGFzdGUwKAogICAgICAgICI8Yj5Hcm91cCBTaXplOjwvYj48L2JyPjwvYnI+IiwgZGYkZ3JvdXBfcGN0LCAKICAgICAgICAiJTwvYnI+PGI+VG90YWwgU2Nob2xhcnM8L2I+PC9icj4iLCBkZiRuKQogIAogICAgIyBEZWZpbmUgZ2dwbG90IG9iamVjdAogICAgCiAgICB2aXN1YWwgPC0gZGYgJT4lIAoKICAgICAgICAjIFJlZGVmaW5lIGdyb3VwIGNvbHVtbiBhcyBmYWN0b3JzIGFuZCBzZXQgb3JkZXIKICAgICAgICAjIE5vdGU6IGluY2x1ZGVzIEhUTUwtZWRpdGVkIHR5cGUgY29sdW1uIHRleHQKICAgICAgCiAgICAgICAgZHBseXI6Om11dGF0ZShncm91cCA9IGZhY3Rvcihncm91cCwgCiAgICAgICAgICAgIGxldmVscyA9IGMoCiAgICAgICAgICAgICAgICAiTWFsZSIsCiAgICAgICAgICAgICAgICAiRmVtYWxlIiwKICAgICAgICAgICAgICAgICJFTEwiLAogICAgICAgICAgICAgICAgIkR1YWwtSWRlbnRpZmllZCIsCiAgICAgICAgICAgICAgICAiU3BlY2lhbCBFZC4iLAogICAgICAgICAgICAgICAgIjUwNCIsCiAgICAgICAgICAgICAgICAiQWZyLiBBbWVyaWNhbiIsCiAgICAgICAgICAgICAgICAiTGF0aW5vIiwKICAgICAgICAgICAgICAgICJXaGl0ZSIsCiAgICAgICAgICAgICAgICAiQXNpYW4iLAogICAgICAgICAgICAgICAgIkFtLiBJbmQvQWxhc2suIE5hdGl2ZSIsCiAgICAgICAgICAgICAgICAiTmF0LiBIYXcvUGFjLiBJc2xhbmQuIikpKSU+JQogICAgICAKICAgICAgICBkcGx5cjo6bXV0YXRlKHR5cGU9ZmFjdG9yKHR5cGUsIAogICAgICAgICAgICBsZXZlbHMgPSBjKAogICAgICAgICAgICAgICAgIjxiPkdlbmRlcjwvYj4iLAogICAgICAgICAgICAgICAgIjxiPlNwZWNpYWwgR3JvdXBzPC9iPiIsCiAgICAgICAgICAgICAgICAiPGI+UmFjZS9FdGhuaWNpdHk8L2I+IikpKSU+JQogIAogICAgICAgICAgICAjIERyYXcgZmlndXJlIHVzaW5nIGdncGxvdAogICAgICAgICAgICAjIE5vdGU6IGdlb21fY29sID09IGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgLi4uKQogICAgICAKICAgICAgICAgICAgZ2dwbG90KGFlcyh4PWZhY3Rvcihncm91cCwgbGV2ZWxzPXJldihsZXZlbHMoZ3JvdXApKSksIHk9Z3JvdXBfcGN0LCAKICAgICAgICAgICAgICAgIGxhYmVsPWxhYmVsX3BlcmNlbnQoZ3JvdXBfcGN0KSwgdGV4dD10b29sdGlwX3RleHQpKSsKICAgICAgICAgICAgICAgIGdlb21fY29sKHBvc2l0aW9uPSJkb2RnZSIsIGNvbG9yPSJibGFjayIsIGFlcyhmaWxsPWFzLmZhY3Rvcih0eXBlKSkpKwogICAgICAgICAgICAgICAgY29vcmRfZmxpcCgpKwogICAgICAKICAgICAgICAgICAgICAgICMgVGl0bGVzIGFuZCBsYWJlbHMgKHRpdGxlIGRlcHJlY2F0ZWQgYnkgcGxvdGx5KQogICAgICAgICAgICAgICAgIyBDb25mbGljdDogZ2VvbV90ZXh0IGxheWVyIHdpdGggZ2VvbV9iYXIsIHJlcGxhY2VkIHdpdGggdG9vbHRpcCBvbmx5CiAgICAgIAogICAgICAgICAgICAgICAgbGFicyh0aXRsZT0iU2Nob2xhciBEZW1vZ3JhcGhpY3MgMjAxOS0yMCIsIAogICAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIlN1bW1hcnkgU3RhdGlzdGljcyBvZiBHZW5kZXIsIFNwZWNpYWwgR3JvdXBzLCBhbmQgUmFjZSBpbiB0aGUgRGlzdHJpY3QiLCAKICAgICAgICAgICAgICAgICAgICB4PSIiLCB5PSIiLAogICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSBwYXN0ZSh0b1N0cmluZyhkZW1vZ3JhcGhpY3MuZGYkZ3JvdXBbZGVtb2dyYXBoaWNzLmRmJG49PTBdKSwgCiAgICAgICAgICAgICAgICAgICAgInNpemVzIG5vdCByZXBvcnRlZC4iKSkrCiAgICAgIAogICAgICAgICAgICAgICAgIyBBeGlzIHNjYWxlIGFuZCBsYWJlbHMKICAgICAgICAgICAgICAgICMgTm90ZTogY29udHJvbHMgbWFyZ2luIG9mIGdyYXBoIGZvciBheGlzIGN1dC1vZmZzIHdpdGggY29uc3RhbnQgKzUKICAgICAgCiAgICAgICAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kPWMoMCwwKSwgCiAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCAoZGYkZ3JvdXBfcGN0JT4lCiAgICAgICAgICAgICAgICAgICAgICAgIHNvcnQoLiwgZGVjcmVhc2luZyA9IFRSVUUpJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAuWzFdKSs1KSwgCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gZnVuY3Rpb24oeCkgcGFzdGUwKHgsICIlIikpICsgIyBbXQogICAgICAKICAgICAgICAgICAgICAgICMgVGhlbWVzIGFuZCBjb2xvcnMgKGxlZ2VuZCBsb2NhdGlvbiBkZXByZWNhdGVkIGJ5IHBsb3RseSB1bmxlc3MgJ25vbmUnKQogICAgICAKICAgICAgICAgICAgICAgIHNjYWxlX2ZpbGxfYnJld2VyKCkrCiAgICAgICAgICAgICAgICB0aGVtZV9idygpKwogICAgICAgICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgICAgICAgICAgICAgbGVnZW5kLmJveD0iaG9yaXpvbnRhbCIsCiAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3I9ImJsYWNrIiwgc2l6ZSA9IDExKSwKICAgICAgICAgICAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChjb2xvcj0iYmxhY2siLCBzaXplID0gMTEpKQogICAgCiAgICAgICAgICAgICAgICAjIEFwcGx5IGdncGxvdGx5IChwbG90bHkpIGZ1bmN0aW9uYWxpdHkgdG8gZ2dwbG90IG9iamVjdAogICAgICAgICAgICAgICAgICAgICMgTm90ZTogdXNlIG9mIEhUTUwgdGV4dCBiZWxvdyByZXF1aXJlZCB0byBjcmVhdGUgYSBzdWJ0aXRsZSBkZXByZWNhdGVkIGJ5IGNvbnZlcnNpb24KICAgICAgICAgICAgICAgICAgICAjIE5vdGU6IHVzZXIgYWNjZXNzIHRvICdtb2RlIGJhcicgY29tcGxldGVseSByZXN0cmljdGVkCiAgICAKICAgICAgICAgICAgICAgIHZpc3VhbCA8LSBnZ3Bsb3RseSh2aXN1YWwsIHRvb2x0aXA9InRleHQiLCB3aWR0aD13aWR0aF9iYXIsIGhlaWdodD1oZWlnaHRfYmFyKSU+JSAKICAgICAgICAgICAgICAgICAgICBjb25maWcoZGlzcGxheU1vZGVCYXIgPSBGQUxTRSklPiUgCiAgICAgICAgICAgICAgICAgICAgbGF5b3V0KHhheGlzPWxpc3QoZml4ZWRyYW5nZT1UUlVFKSklPiUKICAgICAgICAgICAgICAgICAgICBsYXlvdXQoeWF4aXM9bGlzdChmaXhlZHJhbmdlPVRSVUUpKSU+JQogICAgICAgICAgICAgICAgICAgIGxheW91dCh0aXRsZT1saXN0KHRleHQ9cGFzdGUwKAogICAgICAgICAgICAgICAgICAgICAgICAnPGI+U2Nob2xhciBEZW1vZ3JhcGhpY3M8L2I+JywKICAgICAgICAgICAgICAgICAgICAgICAgJzxicj4nLAogICAgICAgICAgICAgICAgICAgICAgICAnPHN1cD4nLAogICAgICAgICAgICAgICAgICAgICAgICAnPGVtPlN1bW1hcnkgb2YgJyxuX3RvdGFsLCcgU2Nob2xhcnMgaW4gdGhlIERpc3RyaWN0ICgyMDE5LTIwKTwvZW0+JywKICAgICAgICAgICAgICAgICAgICAgICAgJzwvc3VwPicpKSklPiUKICAgICAgICAgICAgICAgICAgICBsYXlvdXQoYW5ub3RhdGlvbnM9bGlzdCh4ID0gMSwgeSA9IC0wLjEsIHRleHQgPSAKICAgICAgICAgICAgICAgICAgICAgICAgIkhpZ2ggTmVlZHMgYW5kIEVjb24uIERpc2FkdmFudGFnZWQgZ3JvdXBzIG5vdCByZXBvcnRlZC4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgc2hvd2Fycm93ID0gRkFMU0UsIHhyZWY9J3BhcGVyJywgeXJlZj0ncGFwZXInLCAKICAgICAgICAgICAgICAgICAgICAgICAgeGFuY2hvcj0ncmlnaHQnLCB5YW5jaG9yPSdhdXRvJywgeHNoaWZ0PTAsIHlzaGlmdD0tMTAsCiAgICAgICAgICAgICAgICAgICAgICAgIGZvbnQ9bGlzdChzaXplPTEyLCBjb2xvcj0iYmxhY2siKSkpJT4lCiAgICAgICAgICAgICAgICAgICAgbGF5b3V0KGxlZ2VuZD1saXN0KG9yaWVudGF0aW9uPSJoIiwgeD0wLjI1LCB5PS0wLjIpKSU+JQogICAgICAgICAgICAgICAgICAgIHN0eWxlKHRleHRwb3NpdGlvbj0icmlnaHQiKQogICAgICAgICAgICAgICAgCiAgICByZXR1cm4odmlzdWFsKQp9CgpyZW5kZXJfZGVtb2dyYXBoaWNzKGRlbW9ncmFwaGljcy5kZikKYGBg